﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Core.Rules;
using HtmlAgilityPack;
using System.IO;
using System.Text.RegularExpressions;

namespace Core
{
    public class Question
    {
        public Question() {}
        public Question(string a, string q) : this(a, q, null) {  }
        public Question(string a, string q, bool? markSolved) { this.Author = a; this.QuestionText = q; AnsweredMark = markSolved; }
        public String Author { get; set; }
        public String QuestionText { get; set; }
        public bool? AnsweredMark { get; set; } // this field indicates wether there is mark on the page or not

    }

    public class Reply
    {
        public Reply() {}
        public Reply(string a, string rt) : this(a, rt, false) {  }
        public Reply(string a, string rt, bool containsSourceCode) { this.Author = a; this.ReplyText = rt; ContainsSourceCode = containsSourceCode; }
        public String Author { get; set; }
        public String ReplyText { get; set; }
        public bool ContainsSourceCode { get; set; }
    }

    public class QuestionInformation
    {
        public Question Question { get; set; }
        public List<Reply> Replies { get; set; }

        public QuestionInformation()
        {
            Question = new Question();
            Replies = new List<Reply>();
        }

        public void ParseHTML(SiteEnum site, string html, string message)
        {
            //Just jamming this in here
            //Find the linked and related text
            //Find occurances of the search message in quotes in the linked and related text, remove them
            if (site == SiteEnum.STACK_OVERFLOW && message != "" && false)
            {
                int timesOnPage = Regex.Matches(html, message).Count;
                message = message.Replace("'", "&#39;");
                message = message.Replace(".", "");          //Google doesn't match punctuation, need to do this the innertext too
                HtmlDocument page = new HtmlDocument();
                page.LoadHtml(html);

                HtmlNodeCollection linked = page.DocumentNode.SelectNodes("//div[@class='linked']/div/a");
                HtmlNodeCollection related = page.DocumentNode.SelectNodes("//div[@class='related']/div/a");

                int timesInLinks = 0;

                if (linked != null)
                {
                    foreach (HtmlNode n in linked)
                    {
                        string t = n.InnerText.Replace(".", "");
                        if (t.Contains(message))
                            timesInLinks++;
                    }
                }

                if (related != null)
                {
                    foreach (HtmlNode n in related)
                    {
                        string t = n.InnerText.Replace(".", "");
                        if (t.Contains(message))
                            timesInLinks++;
                    }
                }

                if (timesOnPage == timesInLinks)
                {
                    return;
                }
            }

            //The regular old code

            Replies.Clear(); // need to call it (consider two consecutive calls to ParseHTML)
            switch (site)
            {
                case SiteEnum.STACK_OVERFLOW: parseStackOverflow(html); break;
                case SiteEnum.DEVSHED: parseDevShed(html); break;
                case SiteEnum.CODE_GURU: parseCodeGuru(html); break;
                case SiteEnum.BYTES: parseBytes(html); break;
                case SiteEnum.DANIWEB: parseDaniweb(html);  break;
                default: throw new NotImplementedException();
            }
        }


        /// <summary>
        /// Convert html from "http://www.daniweb.com/..." into question (the first post the thread) and replies (other posts).
        /// Then data saved to Question and Replies
        /// </summary>
        /// <param name="html">input string</param>
        private void parseDaniweb(string html)
        {            
            var postRegex = new Regex("<div\\s[^>]*?id=\"post\\d+\"> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var messageRegex = new Regex("<div\\sid=\"post_message_\\d+\"[^>]*> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var usernameRegex = new Regex("<a\\sclass=\"username\"[^>]*>(?<username>.*?)</a>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            //var removeQuoteRegex = //Found no example of using quoting
            var answeredMarkregex = new Regex("<img src=[^>]*>\\s*Marked\\s*<span>Solved</span>", RegexOptions.Compiled | RegexOptions.Singleline);
            var answerdMarkFound = answeredMarkregex.IsMatch(html); // look for "answered by"
            var sourceCodeRegex = new Regex("<div\\sclass=\"codeblock\"> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var m = postRegex.Matches(html); // extract all posts from the thread
            for (int i = 0; i < m.Count; i++)
            {
                var post = m[i].Value;
                var username = usernameRegex.Match(post).Groups["username"].Value; // get user's name
                var message = messageRegex.Match(post).Value; // get message from the post
                //message = removeQuoteRegex.Replace(message, ""); // remove citations/quotes
                var sourceCode = sourceCodeRegex.IsMatch(message);
                if (sourceCode) message = sourceCodeRegex.Replace(message, "");
                // not sure about the we save data in this class
                if (i == 0)
                {
                    Question = new Question(username, message, answerdMarkFound); // create "Question" (?)
                }
                else Replies.Add(new Reply(username, message, sourceCode));  // add replies
            }
        }

        /// <summary>
        /// Convert html from "http://www.bytes.com/topic..." into question (the first post the thread) and replies (other posts).
        /// Then data saved to Question and Replies
        /// </summary>
        /// <param name="html">input string</param>
        private void parseBytes(string html)
        {            
            var postRegex = new Regex("<td\\s[^>]*?id=\"td_post_\\d+\"> .*? ( ( (?<Open><td[^>]*>) .*?)+ ( (?<Close-Open></td>) .*? )+ )* </td>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var messageRegex = new Regex("<div\\s[^>]*?id=\"post_message_\\d+\"> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var usernameRegex = new Regex("<a\\sclass=\"bigusername\"[^>]*>(?<username>.*?)</a>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var usernameOldFormatRegex = new Regex("<div style=\"font-weight: bold; margin: 0px 0px 0px 0px;\">\\s*<span>\\s*(?<username>[^<>]*?)\\s*</span>\\s*</div>", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase); 
            var removeQuoteRegex = new Regex("<div style=\"margin:20px; margin-top:5px; border-left: 2px #ccc solid; padding-left: 10px; font-size: 90%; color: #666; line-height: 18px; \">.*?(((?<Open><div[^>]*>).*?)+((?<Close-Open></div>).*?)+)*</div>", RegexOptions.Compiled | RegexOptions.Singleline);
            var answeredMarkregex = new Regex("<h4[^>]*>\\&#10003; answered  by.*?</h4>", RegexOptions.Compiled | RegexOptions.Singleline);
            var answerdMarkFound = answeredMarkregex.IsMatch(html); // look for "answered by"
            var sourceCodeRegex = new Regex("<div\\sid=\"codeHolder\"[^>]*> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var m = postRegex.Matches(html); // extract all posts from the thread
            for (int i = 0; i < m.Count; i++)
            {
                var post = m[i].Value;
                var username = usernameRegex.Match(post).Groups["username"].Value; // get user's name
                if (string.IsNullOrWhiteSpace(username)) username = usernameOldFormatRegex.Match(post).Groups["username"].Value; // try to extract username for "old format" of the forum
                var message = messageRegex.Match(post).Value; // get message from the post
                message = removeQuoteRegex.Replace(message, ""); // remove citations/quotes
                var sourceCode = sourceCodeRegex.IsMatch(message);
                if (sourceCode) message = sourceCodeRegex.Replace(message, "");
                // not sure about the we save data in this class
                if (i == 0)
                {
                    Question = new Question(username, message, answerdMarkFound); // create "Question" (?)
                }
                else Replies.Add(new Reply(username, message, sourceCode)); // add replies
            }
        }

        /// <summary>
        /// Convert html from "http://www.codeguru.com/forum/showthread.php..." into question (the first post the thread) and replies (other posts).
        /// Then data saved to Question and Replies
        /// </summary>
        /// <param name="html">input string</param>
        private void parseCodeGuru(string html)
        {            
            var postRegex = new Regex("<div\\sid=\"edit\\d+\" [^>]*> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var messageRegex = new Regex("<div\\sid=\"post_message_\\d+\"> .*? ( ( (?<Open><div[^>]*>) .*?)+ ( (?<Close-Open></div>) .*? )+ )* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var usernameRegex = new Regex("<a\\sclass=\"bigusername\"[^>]*>(?<username>.*?)</a>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var removeQuoteRegex = new Regex("<div[^>]*>[\\s]*<div[^>]*>Quote:</div>[\\s]*<table[^>]*> .*? ( ( (?<Open><table[^>]*>) .*?)+ ( (?<Close-Open></table>) .*? )+ )* </table>[\\s]*</div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var sourceCodeRegex = new Regex("<div[^>]*> \\s* <div[^>]*> \\s* Code: \\s* </div> \\s* <pre[^>]*> .*? ( ( (?<Open><pre[^>]*>) .*?)+ ( (?<Close-Open></pre>) .*? )+ )* </pre> \\s* </div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var m = postRegex.Matches(html); // extract all posts from the thread
            for (int i = 0; i < m.Count; i++)
            {
                var post = m[i].Value;
                var username = usernameRegex.Match(post).Groups["username"].Value; // get user's name
                var message = messageRegex.Match(post).Value; // get message from the post
                message = removeQuoteRegex.Replace(message, ""); // remove citations/quotes
                var sourceCode = sourceCodeRegex.IsMatch(message);
                if (sourceCode) message = sourceCodeRegex.Replace(message, "");
                // not sure about the we save data in this class
                if (i == 0)
                {
                    Question = new Question(username, message); // create "Question" (?)
                }
                else Replies.Add(new Reply(username, message, sourceCode)); // add replies
            }
        }

        private void parseDevShed(string html)
        {
            HtmlDocument page = new HtmlDocument();
            page.LoadHtml(html);

            HtmlNodeCollection posts = page.DocumentNode.SelectNodes("//table[starts-with(@id,'post')]");

            HtmlNode question = null;
            try
            {
                question = posts[0];
            }
            catch (Exception e)
            {
                this.Question = new Question("", "");
                return;
            }
           
            String questionAuthor = "";
            try
            {
                questionAuthor = question.SelectSingleNode(".//div[starts-with(@id,'postmenu')]/table/tr/td/a").InnerText;
            }
            catch (Exception e)
            {
                questionAuthor = question.SelectSingleNode(".//div[starts-with(@id,'postmenu')]/table/tr/td/b/a/font/u").InnerText;
            }

            //The sentiment analysis might want you to pass in a proper formatted document... so we might
            //need to transform the html to proper text
            String questionText = question.SelectSingleNode(".//span[@id='intelliTxt']/div").InnerText;

            //Devshed doesn't have an answered thing
            //bool answered = (page.DocumentNode.SelectSingleNode("//div[@class='acceptedCTA']") != null);
            //if (answered == false)
            //    answered = (page.DocumentNode.SelectSingleNode("//div[@class='acceptedCTA acceptedCTAVerified']") != null);

            this.Question = new Question(questionAuthor, questionText);

            //Now get a collection of every reply
            for (int i = 1; i < posts.Count; i++)
            {
                HtmlNode reply = posts[i];
                string rAuthor = "";

                try
                {
                    rAuthor = reply.SelectSingleNode(".//div[starts-with(@id,'postmenu')]/table/tr/td/a").InnerText;
                }
                catch (Exception e)
                {
                    rAuthor = reply.SelectSingleNode(".//div[starts-with(@id,'postmenu')]/table/tr/td/b/a/font/u").InnerText;
                }

                string replyText = reply.SelectSingleNode(".//span[@id='intelliTxt']/div").InnerText;
                bool hasCode = (reply.SelectNodes(".//pre") != null);
                this.Replies.Add(new Reply(rAuthor, replyText, hasCode));
            }
        }

	    private void parseStackOverflow(string html)
	    {
            //string html = "asdasdas<td class=\"postcell\">asdasdadasdasdsdasqw<div class=\"post-text\">fsdfsdfsdfdsfsdfsdfsdwedcxcsfwcscsdfws<a href=\"/users/232131/Rahul\">asdasd</div>asdfadasd</td>asdsdadasd<td class=\"comment-text\">asd<div>sfsdfwesfsdfsdfewfwsfsdf<a href=\"/users/12321/Rahul7\"asdfdfsd</div>ad</td>asdas";
                        
            // Post stores Message + Username, Post is simply all answers + opening posts (Comments not included)
            var postRegex = new Regex("<td\\sclass=\"(postcell|answercell)\">.*?((?<Open><td[^>]*>.*?)+((?<Close-Open></td>).*?)+)*</td>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var messageRegex = new Regex("<div\\sclass=\"post-text\"> .*?((?<Open><div[^>]*>.*?)+((?<Close-Open></div>).*?)+)*</div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);

            // Comment stores the comment text + Username
            var commentRegex = new Regex("<td\\sclass=\"comment-text\">.*?((?<Open><td[^>]*>.*?)+((?<Close-Open></td>).*?)+)*</td>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            var commentTextRegex = new Regex("<div>.*?((?<Open><div[^>]*>.*?)+((?<Close-Open></div>).*?)+)*</div>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            
            // Username for post or comment is in same format
            var usernameRegex = new Regex("<a\\shref=\"/users/\\d+/.*\"[^>]*>(?<username>.*?)</a>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);
            
            // Every code element is within <code> tag
            var sourceCodeRegex = new Regex("<code>", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);

            // Every green tick is in a 'vote accepted' class
            var answeredFlagRegex = new Regex("span class=\"vote-accepted-on\"", RegexOptions.Compiled | RegexOptions.Singleline);

            var m = postRegex.Matches(html); // extract messages posts from the thread
            var n = commentRegex.Matches(html); // extract all comments from the thread	
            var answeredFlag = answeredFlagRegex.IsMatch(html); // check if answered mark exists

            for (int i = 0; i < (m.Count + n.Count); i++)
            {
                if (i < m.Count)
                {
                    var post = m[i].Value;  // Store all answers first
                    var message = messageRegex.Match(post).Value; // get message from the answer/question
                    var username = usernameRegex.Match(post).Groups["username"].Value;
                    var sourceCode = sourceCodeRegex.IsMatch(post);

                    if (i == 0)
                    {
                            Question = new Question(username, message, answeredFlag); // create "Question" (?)
                    }

                    else Replies.Add(new Reply(username, message, sourceCode)); // add replies*/
                }

                else
                {
                     var comment = n[i - m.Count].Value;   // Store all comments later
                     var commentText = commentTextRegex.Match(comment).Value; // get message from the comment
                     var username = usernameRegex.Match(comment).Groups["username"].Value;
                     var sourceCode = sourceCodeRegex.IsMatch(comment);
                     Replies.Add(new Reply(username, commentText,sourceCode)); // add replies*/
                }  
            }
        }
    }
}
